//
//  Pennyworth_Punch_Clock_AppDelegate.m
//  Pennyworth Punch Clock
//
//  Created by Chris Karr on 6/30/08.
//  Copyright Northwestern University 2008 . All rights reserved.
//

#import "AppDelegate.h"
#import "AppleScriptStream.h"

@implementation AppDelegate

@synthesize lastSlices;
@synthesize selectedSliceTag;

- (IBAction) closeDatePicker:(id) sender
{
	[self willChangeValueForKey:@"sliceFilter"];

	[NSApp endSheet:datePanel];
	[datePanel orderOut:sender];

	[self didChangeValueForKey:@"sliceFilter"];
}

- (void) activateStatusMenu
{
    NSStatusBar * bar = [NSStatusBar systemStatusBar];
	
    theItem = [bar statusItemWithLength:NSVariableStatusItemLength];
    [theItem retain];
	
    [theItem setTitle:nil];
    [theItem setImage:[NSImage imageNamed:@"ppc-status"]];
    [theItem setHighlightMode:YES];
    [theItem setMenu:statusMenu];
}

 - (void) initPennyworth
 {
	 NSString * detectAs = @"tell application \"System Events\" to return count (every process whose name is \"Pennyworth\")";

	 NSAppleScript * detectScript = [[NSAppleScript alloc] initWithSource:detectAs];
	 
	 NSAppleEventDescriptor * detectDesc = [detectScript executeAndReturnError:nil];
	 
	  if ([[detectDesc stringValue] intValue] == 0)
		  return;
	 
	 NSArray * keys = [NSArray arrayWithObjects:@"Location", @"Social Context", @"Activity", nil]; 

	 for (NSString * key in keys)
	 {
		 NSString * as = [NSString stringWithFormat:@"tell application \"Pennyworth\" to return value of prediction named \"%@\"", key, nil];
		 NSAppleScript * script = [[NSAppleScript alloc] initWithSource:as];
 
		 NSAppleEventDescriptor * desc = [script executeAndReturnError:nil];

		 NSString * prediction = [desc stringValue];
 
		 [script release];
 
		 NSMutableDictionary * note = [NSMutableDictionary dictionary];
		 [note setValue:key forKey:KEY];
		 [note setValue:prediction forKey:PREDICTION];
		 
		 [[NSNotificationCenter defaultCenter] postNotificationName:PREDICTION_FETCHED object:self userInfo:note];
	 }
}

- (void) hotKeyReleased:(NDHotKeyEvent *) aHotKey
{
	[NSApp activateIgnoringOtherApps:YES];
	[updatePanel makeKeyAndOrderFront:nil];
}

- (void) awakeFromNib
{
	[NDHotKeyEvent setSignature:'PPCc'];
	
	hotKey = [[NDHotKeyEvent hotKeyWithKeyCode:35 character:112 modifierFlags:(NSControlKeyMask|NSCommandKeyMask)] retain];
	[hotKey setTarget:self selectorReleased:@selector(hotKeyReleased:) selectorPressed:@selector(hotKeyReleased:)];
	[hotKey setEnabled:YES];
	
	self.lastSlices = [NSMutableDictionary dictionary];
	self.selectedSliceTag = [NSNumber numberWithInteger:0];
	
	[[NSDistributedNotificationCenter defaultCenter] addObserver:self 
														selector:@selector(update:) 
															name:PREDICTION_FETCHED 
														  object:nil];

	[[NSNotificationCenter defaultCenter] addObserver:self 
											 selector:@selector(update:) 
												 name:PREDICTION_FETCHED 
											   object:nil];

	[[NSUserDefaults standardUserDefaults] addObserver:self 
											forKeyPath:START_AT_LOGIN 
											   options:0 
											   context:NULL];

	NSSortDescriptor * descriptor = [[NSSortDescriptor alloc] initWithKey:@"startDate" ascending:NO];
	[slicesController setSortDescriptors:[NSArray arrayWithObject:descriptor]];
	[descriptor release];

	descriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
	[streamsController setSortDescriptors:[NSArray arrayWithObject:descriptor]];
	[descriptor release];
	
	[self initPennyworth];
	[self activateStatusMenu];
	
	NSDateFormatter * formatter = [[NSDateFormatter alloc] initWithDateFormat:@"%1I:%M:%S %p (%B %e, %Y)" allowNaturalLanguage:YES];
	[startDateField setFormatter:formatter];
	[formatter release];

	formatter = [[NSDateFormatter alloc] initWithDateFormat:@"%1I:%M:%S %p (%B %e, %Y)" allowNaturalLanguage:YES];
	[endDateField setFormatter:formatter];
	[formatter release];
}

- (void) startAtLogin
{
	BOOL enabled = [[NSUserDefaults standardUserDefaults] boolForKey:START_AT_LOGIN];
	
	OSStatus status;
	CFArrayRef loginItems = NULL;
	NSURL * url = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
	
	int existingLoginItemIndex = -1;
	
	status = LIAECopyLoginItems (&loginItems);
	
	if (status == noErr) 
	{
		NSEnumerator * enumerator = [(NSArray *) loginItems objectEnumerator];
		NSDictionary * loginItemDict;
		
		while ((loginItemDict = [enumerator nextObject])) 
		{
			if ([[loginItemDict objectForKey:(NSString *) kLIAEURL] isEqual:url]) 
			{
				existingLoginItemIndex = [(NSArray *) loginItems indexOfObjectIdenticalTo:loginItemDict];
				break;
			}
		}
	}
	
	if (enabled && (existingLoginItemIndex == -1))
		LIAEAddURLAtEnd ((CFURLRef) url, false);
	else if (!enabled && (existingLoginItemIndex != -1))
		LIAERemove (existingLoginItemIndex);
	
	if (loginItems)
		CFRelease (loginItems);
}

- (void) observeValueForKeyPath: (NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
	if ([keyPath isEqual:START_AT_LOGIN])
		[self startAtLogin];
}

- (NSNumber *) getSliceWindowTag
{
	return self.selectedSliceTag;
}

- (void) setSliceWindowTag:(NSNumber *) tag
{
	[self willChangeValueForKey:@"sliceFilter"];
	
	self.selectedSliceTag = tag; 

	[self didChangeValueForKey:@"sliceFilter"];
	
	if ([tag integerValue] == 99)
	{
		[NSApp beginSheet:datePanel modalForWindow:window modalDelegate:nil didEndSelector:nil contextInfo:NULL];
	}
}

- (IBAction) invokeDatePanel:(id) sender
{
	[self setSliceWindowTag:[NSNumber numberWithInteger:99]];
}

- (NSPredicate *) getSliceFilter
{
	NSInteger tag = [self.selectedSliceTag integerValue];
	
	NSCalendar * calendar = [NSCalendar currentCalendar];

	NSDate * date = nil;
	
	NSDateComponents * dateComps = [[NSDateComponents alloc] init];
	NSDate * now = [NSDate date];
	
	if (tag == 0)
	{
		[dateComps setMinute:-30];
		date = [calendar dateByAddingComponents:dateComps toDate:now options:0];
	}
	else if (tag == 1)
	{
		[dateComps setHour:-1];
		date = [calendar dateByAddingComponents:dateComps toDate:now options:0];
	}
	else if (tag == 2)
	{
		[dateComps setDay:-1];
		date = [calendar dateByAddingComponents:dateComps toDate:now options:0];
	}
	else if (tag == 3)
	{
		[dateComps setWeek:-1];
		date = [calendar dateByAddingComponents:dateComps toDate:now options:0];
	}
	else if (tag == 4)
	{
		[dateComps setMonth:-1];
		date = [calendar dateByAddingComponents:dateComps toDate:now options:0];
	}
	else if (tag == 99)
	{
		[dateComps release];

		NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
		
		NSDate * start = [defaults objectForKey:@"Custom_Start"];
		NSDate * end = [defaults objectForKey:@"Custom_End"];
		
		if (end == nil)
			end = [NSDate date];
		
		if (start == nil)
			start = [NSDate distantPast];
		
		NSPredicate * predicate = [NSPredicate predicateWithFormat:@"(%K > %@ && %K < %@) || (%K > %@ && %K < %@) || (%K < %@ && %K > %@)", 
				@"startDate", start, @"startDate", end, 
				@"endDate", start, @"endDate", end, 
				@"startDate", start, @"endDate", end, nil];
		
		return predicate;
	}

	[dateComps release];

	if (date == nil)
		return nil;
	else
		return [NSPredicate predicateWithFormat:@"((%K > %@) || (%K > %@) || (%K == NIL))", @"startDate", date, @"endDate", date, @"endDate", nil];
}

- (void) setSliceFilter:(NSPredicate *) predicate
{
	
}

- (void) update:(NSNotification *) theNote
{
	NSDictionary * userInfo = [theNote userInfo];
	
	NSString * key = [userInfo valueForKey:KEY];

	NSString * predictionString = [userInfo valueForKey:PREDICTION];
	
	NSArray * predictions = nil;
	 
	 if ([predictionString rangeOfString:@";"].location != NSNotFound)
		 predictions = [[predictionString stringByReplacingOccurrencesOfString:@"; " withString:@";"] componentsSeparatedByString:@";"];
	 else
		 predictions = [NSArray arrayWithObject:predictionString];

	NSDate * now = [NSDate date];

	if (key != nil && predictions != nil)
	{
		NSFetchRequest * request = [[NSFetchRequest alloc] init];
	
		[request setEntity:[NSEntityDescription entityForName:@"TimeStream" 
									   inManagedObjectContext:[self managedObjectContext]]];
		 
		[request setPredicate:[NSPredicate predicateWithFormat:@"name MATCHES %@", key, nil]];
		
		NSManagedObject * timeStream = nil;

		NSError * error = nil;
	
		NSArray * timeStreams = [[self managedObjectContext] executeFetchRequest:request error:&error];
	
		for (NSManagedObject * stream in timeStreams)
			timeStream = stream;
		
		if (timeStream == nil)
		{
			timeStream = [NSEntityDescription insertNewObjectForEntityForName:@"TimeStream" 
													   inManagedObjectContext:[self managedObjectContext]];
			
			[timeStream setValue:key forKey:@"name"];
			[timeStream setValue:[NSNumber numberWithBool:NO] forKey:@"userEditable"];
		}
		
		NSMutableSet * slices = [timeStream mutableSetValueForKey:@"slices"];
		
		NSMutableArray * activeSlices = [NSMutableArray array];
		
		for (NSString * prediction in predictions)
		{
			BOOL newSlice = YES;
			
			for (NSManagedObject * slice in slices)
			{
				if ([slice valueForKey:@"endDate"] == nil && [prediction isEqualToString:[slice valueForKey:@"name"]])
				{
					newSlice = NO;
					[activeSlices addObject:slice];
				}
			}

			if (newSlice && [prediction length] > 0)
			{
				NSManagedObject * slice = [NSEntityDescription insertNewObjectForEntityForName:@"Slice"	
																		inManagedObjectContext:[self managedObjectContext]];
				
				[slice setValue:prediction forKey:@"name"];
				[slice setValue:now forKey:@"startDate"];
				
				[slices addObject:slice];
				[activeSlices addObject:slice];
			}
		}

		for (NSManagedObject * slice in slices)
		{
			if ([slice valueForKey:@"endDate"] == nil)
			{
				NSString * sliceName = [slice valueForKey:@"name"];
				
				if (![predictions containsObject:sliceName])
					[slice setValue:now forKey:@"endDate"];
			}
		}
		
		[self.lastSlices setValue:activeSlices forKey:key];

		NSMutableSet * values = [timeStream mutableSetValueForKey:@"values"];

		BOOL hasValue = NO;
		
		for (NSString * prediction in predictions)
		{
			for (NSManagedObject * value in values)
			{
				NSString * valueName = [value valueForKey:@"name"];
			
				if ([valueName isEqualToString:prediction])
					hasValue = YES;
			}

			if (!hasValue)
			{	
				NSManagedObject * value = [NSEntityDescription insertNewObjectForEntityForName:@"Value"	
																		inManagedObjectContext:[self managedObjectContext]];
		
				[value setValue:prediction forKey:@"name"];
				[values addObject:value];
			} 
		}
	}
}

/**
    Returns the support folder for the application, used to store the Core Data
    store file.  This code uses a folder named "Pennyworth_Punch_Clock" for
    the content, either in the NSApplicationSupportDirectory location or (if the
    former cannot be found), the system's temporary directory.
 */

- (NSString *)applicationSupportFolder 
{
    NSArray * paths = NSSearchPathForDirectoriesInDomains (NSApplicationSupportDirectory, NSUserDomainMask, YES);
    NSString * basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : NSTemporaryDirectory ();
    return [basePath stringByAppendingPathComponent:@"Pennyworth Punch Clock"];
}


/**
    Creates, retains, and returns the managed object model for the application 
    by merging all of the models found in the application bundle.
 */
 
- (NSManagedObjectModel *) managedObjectModel 
{

    if (managedObjectModel != nil)
        return managedObjectModel;
	
    managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];    
    return managedObjectModel;
}


/**
    Returns the persistent store coordinator for the application.  This 
    implementation will create and return a coordinator, having added the 
    store for the application to it.  (The folder for the store is created, 
    if necessary.)
 */

- (NSPersistentStoreCoordinator *) persistentStoreCoordinator 
{

    if (persistentStoreCoordinator != nil) 
        return persistentStoreCoordinator;

    NSFileManager * fileManager;
    NSString * applicationSupportFolder = nil;
    NSURL * url;
    NSError * error;
    
    fileManager = [NSFileManager defaultManager];
    applicationSupportFolder = [self applicationSupportFolder];
    
	if ( ![fileManager fileExistsAtPath:applicationSupportFolder isDirectory:NULL] ) 
	{
        [fileManager createDirectoryAtPath:applicationSupportFolder attributes:nil];
    }
    
    url = [NSURL fileURLWithPath:[applicationSupportFolder stringByAppendingPathComponent:@"Pennyworth_Punch_Clock.xml"]];
	
    persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
	
    if (![persistentStoreCoordinator addPersistentStoreWithType:NSXMLStoreType configuration:nil URL:url options:nil error:&error])
        [[NSApplication sharedApplication] presentError:error];

    return persistentStoreCoordinator;
}


/**
    Returns the managed object context for the application (which is already
    bound to the persistent store coordinator for the application.) 
 */
 
- (NSManagedObjectContext *) managedObjectContext {

    if (managedObjectContext != nil)
        return managedObjectContext;

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];

    if (coordinator != nil) 
	{
        managedObjectContext = [[NSManagedObjectContext alloc] init];
        [managedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    
    return managedObjectContext;
}


/**
    Returns the NSUndoManager for the application.  In this case, the manager
    returned is that of the managed object context for the application.
 */
 
- (NSUndoManager *) windowWillReturnUndoManager:(NSWindow *) window 
{
    return [[self managedObjectContext] undoManager];
}


/**
    Performs the save action for the application, which is to send the save:
    message to the application's managed object context.  Any encountered errors
    are presented to the user.
 */
 
- (IBAction) saveAction:(id) sender 
{
    NSError * error = nil;

    if (![[self managedObjectContext] save:&error])
        [[NSApplication sharedApplication] presentError:error];
}


/**
    Implementation of the applicationShouldTerminate: method, used here to
    handle the saving of changes in the application managed object context
    before the application terminates.
 */
 
- (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication *) sender 
{
	[NSApp activateIgnoringOtherApps:YES];
		
	if (NSAlertDefaultReturn == NSRunAlertPanel (@"Quit Pennyworth Punch Clock?", @"Do you want to stop running Pennyworth Punch Clock?", @"No", @"Yes", nil))
		return NO;
	
	NSDate * now = [NSDate date];
	
	for (NSString * key in [self.lastSlices allKeys])
	{
		for (NSManagedObject * slice in [self.lastSlices valueForKey:key])
			[slice setValue:now forKey:@"endDate"];
	}
	
    NSError * error;
    int reply = NSTerminateNow;
    
    if (managedObjectContext != nil) 
	{
        if ([managedObjectContext commitEditing]) 
		{
            if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) 
			{
				
                // This error handling simply presents error information in a panel with an 
                // "Ok" button, which does not include any attempt at error recovery (meaning, 
                // attempting to fix the error.)  As a result, this implementation will 
                // present the information to the user and then follow up with a panel asking 
                // if the user wishes to "Quit Anyway", without saving the changes.

                // Typically, this process should be altered to include application-specific 
                // recovery steps.  

                BOOL errorResult = [[NSApplication sharedApplication] presentError:error];
				
                if (errorResult == YES)
                    reply = NSTerminateCancel;

                else
				{
                    int alertReturn = NSRunAlertPanel(nil, @"Could not save changes while quitting. Quit anyway?" , @"Quit anyway", @"Cancel", nil);

                    if (alertReturn == NSAlertAlternateReturn) 
                        reply = NSTerminateCancel;	
                }
            }
        } 
        else
            reply = NSTerminateCancel;
    }
	
	[[NSFileManager defaultManager] removeItemAtPath:TMP_DIR error:NULL];
    
    return reply;
}


/**
    Implementation of dealloc, to release the retained variables.
 */
 
- (void) dealloc 
{
    [managedObjectContext release], managedObjectContext = nil;
    [persistentStoreCoordinator release], persistentStoreCoordinator = nil;
    [managedObjectModel release], managedObjectModel = nil;
    [super dealloc];
}

- (IBAction) toggleLog:(id) sender
{
	[NSApp activateIgnoringOtherApps:YES];
	[window makeKeyAndOrderFront:sender];
	[mainTabs selectTabViewItemAtIndex:0];
}
	
- (IBAction) toggleReports:(id) sender
{
	[NSApp activateIgnoringOtherApps:YES];
	[window makeKeyAndOrderFront:sender];
	[mainTabs selectTabViewItemAtIndex:1];
}

- (IBAction) togglePreferences:(id) sender
{
	[NSApp activateIgnoringOtherApps:YES];
	[window makeKeyAndOrderFront:sender];
	[mainTabs selectTabViewItemAtIndex:2];
}

- (BOOL) application:(NSApplication *) sender delegateHandlesKey:(NSString *) key
{
	if ([key isEqualToString:@"streams"])
		return YES;
	
	return NO;
}

- (NSArray *) getStreams
{
	NSMutableArray * asStreams = [NSMutableArray array];
	
	for (NSManagedObject * stream in [streamsController arrangedObjects])
	{
		AppleScriptStream * asStream = [[AppleScriptStream alloc] init];
		
		asStream.name = [stream valueForKey:@"name"];
		asStream.object = stream;
		
		[asStreams addObject:asStream];
		
		[asStream release];
	}
	
	return asStreams;
}

- (NSNumber *) getSelectedSliceTag
{
	return self.selectedSliceTag;
}

- (BOOL) applicationShouldHandleReopen:(NSApplication *) theApplication hasVisibleWindows:(BOOL) flag
{
	[self toggleLog:self];
	
	return NO;
}

- (NSDictionary *) getActiveState
{
	NSPredicate * predicate = [NSPredicate predicateWithFormat:@"(%K == NIL)", @"endDate", nil];
	
	NSFetchRequest * request = [[NSFetchRequest alloc] init];
	
	[request setEntity:[NSEntityDescription entityForName:@"Slice" inManagedObjectContext:[self managedObjectContext]]];
	
	[request setPredicate:predicate];
	
	NSError * error = nil;
	
	NSArray * slices = [[self managedObjectContext] executeFetchRequest:request error:&error];
	
	NSMutableDictionary * state = [NSMutableDictionary dictionary];
	
	for (NSManagedObject * slice in slices)
	{
		NSString * streamName = [[slice valueForKey:@"stream"] valueForKey:@"name"];

		if ([state valueForKey:streamName] == nil)
			[state setValue:[NSMutableArray array] forKey:streamName];

		[[state valueForKey:streamName] addObject:[slice valueForKey:@"name"]];
	}

	[request release];
	
	return state;
}

@end
